package net.minecraft.client.gl;

import com.mojang.blaze3d.opengl.GlConst;
import com.mojang.blaze3d.opengl.GlStateManager;
import java.nio.ByteBuffer;
import java.util.Set;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.ARBBufferStorage;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GLCapabilities;

@Environment(EnvType.CLIENT)
public abstract class BufferManager {
	public static BufferManager create(GLCapabilities capabilities, Set<String> usedCapabilities) {
		if (capabilities.GL_ARB_direct_state_access && GlBackend.allowGlArbDirectAccess) {
			usedCapabilities.add("GL_ARB_direct_state_access");
			return new BufferManager.ARBBufferManager();
		} else {
			return new BufferManager.DefaultBufferManager();
		}
	}

	abstract int createBuffer();

	abstract void setBufferData(int buffer, long size, int usage);

	abstract void setBufferData(int buffer, ByteBuffer data, int usage);

	abstract void setBufferSubData(int buffer, int offset, ByteBuffer data);

	abstract void setBufferStorage(int buffer, long size, int flags);

	abstract void setBufferStorage(int buffer, ByteBuffer data, int flags);

	@Nullable
	abstract ByteBuffer mapBufferRange(int buffer, int offset, int length, int access);

	abstract void unmapBuffer(int buffer);

	abstract int createFramebuffer();

	abstract void setupFramebuffer(int framebuffer, int colorAttachment, int depthAttachment, int mipLevel, int bindTarget);

	abstract void setupBlitFramebuffer(
		int readFramebuffer, int writeFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter
	);

	abstract void flushMappedBufferRange(int buffer, int offset, int length);

	abstract void method_72237(int i, int j, int k, int l, int m);

	@Environment(EnvType.CLIENT)
	static class ARBBufferManager extends BufferManager {
		@Override
		int createBuffer() {
			return ARBDirectStateAccess.glCreateBuffers();
		}

		@Override
		void setBufferData(int buffer, long size, int usage) {
			ARBDirectStateAccess.glNamedBufferData(buffer, size, usage);
		}

		@Override
		void setBufferData(int buffer, ByteBuffer data, int usage) {
			ARBDirectStateAccess.glNamedBufferData(buffer, data, usage);
		}

		@Override
		void setBufferSubData(int buffer, int offset, ByteBuffer data) {
			ARBDirectStateAccess.glNamedBufferSubData(buffer, (long)offset, data);
		}

		@Override
		void setBufferStorage(int buffer, long size, int flags) {
			ARBDirectStateAccess.glNamedBufferStorage(buffer, size, flags);
		}

		@Override
		void setBufferStorage(int buffer, ByteBuffer data, int flags) {
			ARBDirectStateAccess.glNamedBufferStorage(buffer, data, flags);
		}

		@Nullable
		@Override
		ByteBuffer mapBufferRange(int buffer, int offset, int length, int access) {
			return ARBDirectStateAccess.glMapNamedBufferRange(buffer, offset, length, access);
		}

		@Override
		void unmapBuffer(int buffer) {
			ARBDirectStateAccess.glUnmapNamedBuffer(buffer);
		}

		@Override
		public int createFramebuffer() {
			return ARBDirectStateAccess.glCreateFramebuffers();
		}

		@Override
		public void setupFramebuffer(int framebuffer, int colorAttachment, int depthAttachment, int mipLevel, int bindTarget) {
			ARBDirectStateAccess.glNamedFramebufferTexture(framebuffer, GlConst.GL_COLOR_ATTACHMENT0, colorAttachment, mipLevel);
			ARBDirectStateAccess.glNamedFramebufferTexture(framebuffer, GlConst.GL_DEPTH_ATTACHMENT, depthAttachment, mipLevel);
			if (bindTarget != 0) {
				GlStateManager._glBindFramebuffer(bindTarget, framebuffer);
			}
		}

		@Override
		public void setupBlitFramebuffer(
			int readFramebuffer, int writeFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter
		) {
			ARBDirectStateAccess.glBlitNamedFramebuffer(readFramebuffer, writeFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
		}

		@Override
		void flushMappedBufferRange(int buffer, int offset, int length) {
			ARBDirectStateAccess.glFlushMappedNamedBufferRange(buffer, offset, length);
		}

		@Override
		void method_72237(int i, int j, int k, int l, int m) {
			ARBDirectStateAccess.glCopyNamedBufferSubData(i, j, k, l, m);
		}
	}

	@Environment(EnvType.CLIENT)
	static class DefaultBufferManager extends BufferManager {
		@Override
		int createBuffer() {
			return GlStateManager._glGenBuffers();
		}

		@Override
		void setBufferData(int buffer, long size, int usage) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			GlStateManager._glBufferData(GlConst.GL_COPY_WRITE_BUFFER, size, GlConst.bufferUsageToGlEnum(usage));
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void setBufferData(int buffer, ByteBuffer data, int usage) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			GlStateManager._glBufferData(GlConst.GL_COPY_WRITE_BUFFER, data, GlConst.bufferUsageToGlEnum(usage));
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void setBufferSubData(int buffer, int offset, ByteBuffer data) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			GlStateManager._glBufferSubData(GlConst.GL_COPY_WRITE_BUFFER, offset, data);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void setBufferStorage(int buffer, long size, int flags) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			ARBBufferStorage.glBufferStorage(GlConst.GL_COPY_WRITE_BUFFER, size, flags);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void setBufferStorage(int buffer, ByteBuffer data, int flags) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			ARBBufferStorage.glBufferStorage(GlConst.GL_COPY_WRITE_BUFFER, data, flags);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Nullable
		@Override
		ByteBuffer mapBufferRange(int buffer, int offset, int length, int access) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			ByteBuffer byteBuffer = GlStateManager._glMapBufferRange(GlConst.GL_COPY_WRITE_BUFFER, offset, length, access);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
			return byteBuffer;
		}

		@Override
		void unmapBuffer(int buffer) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			GlStateManager._glUnmapBuffer(GlConst.GL_COPY_WRITE_BUFFER);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void flushMappedBufferRange(int buffer, int offset, int length) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, buffer);
			GL30.glFlushMappedBufferRange(GlConst.GL_COPY_WRITE_BUFFER, offset, length);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		void method_72237(int i, int j, int k, int l, int m) {
			GlStateManager._glBindBuffer(GlConst.GL_COPY_READ_BUFFER, i);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, j);
			GL31.glCopyBufferSubData(36662, 36663, k, l, m);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_READ_BUFFER, 0);
			GlStateManager._glBindBuffer(GlConst.GL_COPY_WRITE_BUFFER, 0);
		}

		@Override
		public int createFramebuffer() {
			return GlStateManager.glGenFramebuffers();
		}

		@Override
		public void setupFramebuffer(int framebuffer, int colorAttachment, int depthAttachment, int mipLevel, int bindTarget) {
			int i = bindTarget == 0 ? GlConst.GL_DRAW_FRAMEBUFFER : bindTarget;
			int j = GlStateManager.getFrameBuffer(i);
			GlStateManager._glBindFramebuffer(i, framebuffer);
			GlStateManager._glFramebufferTexture2D(i, GlConst.GL_COLOR_ATTACHMENT0, GlConst.GL_TEXTURE_2D, colorAttachment, mipLevel);
			GlStateManager._glFramebufferTexture2D(i, GlConst.GL_DEPTH_ATTACHMENT, GlConst.GL_TEXTURE_2D, depthAttachment, mipLevel);
			if (bindTarget == 0) {
				GlStateManager._glBindFramebuffer(i, j);
			}
		}

		@Override
		public void setupBlitFramebuffer(
			int readFramebuffer, int writeFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter
		) {
			int i = GlStateManager.getFrameBuffer(GlConst.GL_READ_FRAMEBUFFER);
			int j = GlStateManager.getFrameBuffer(GlConst.GL_DRAW_FRAMEBUFFER);
			GlStateManager._glBindFramebuffer(GlConst.GL_READ_FRAMEBUFFER, readFramebuffer);
			GlStateManager._glBindFramebuffer(GlConst.GL_DRAW_FRAMEBUFFER, writeFramebuffer);
			GlStateManager._glBlitFrameBuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
			GlStateManager._glBindFramebuffer(GlConst.GL_READ_FRAMEBUFFER, i);
			GlStateManager._glBindFramebuffer(GlConst.GL_DRAW_FRAMEBUFFER, j);
		}
	}
}
